/*
 * @(#)CipherUtil.java
 *
 * Copyright 2012 by ShenY BIS, All rights reserved.
 */

package com.bis.framework.util.string;

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.MessageDigest;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;

import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
 * CipherUtil 字符串加密类。MD5 DES
 * 
 * @author BIS 2012-12-24
 */
public class CipherUtil
{

    public enum Algorithm
    {
        MD5, DES
    }

    private final String      _encode       = "UTF-8";

    private Algorithm         _algorithm    = null;

    private final byte[]      _key          = this.getEnKey("Cipher");

    private static CipherUtil md5CipherUtil = null;

    private static void md5Construtor()
    {
        try
        {
            md5CipherUtil = new CipherUtil(Algorithm.MD5);
        }
        catch (InvalidAlgorithmParameterException e)
        {
            e.printStackTrace();
        }
    }

    public static CipherUtil getMD5Instance()
    {
        if (null == md5CipherUtil)
            md5Construtor();
        return md5CipherUtil;
    }

    public CipherUtil(Algorithm algorithm) throws InvalidAlgorithmParameterException
    {
        if (algorithm == null)
        {
            throw new InvalidAlgorithmParameterException("Algorithm is empty, it must be given.");
        }

        this._algorithm = algorithm;
    }

    public String Encrypt(String text) throws InvalidAlgorithmParameterException, UnsupportedEncodingException
    {

        String cipher = null;

        if (text == null || "".equals(text))
        {
            throw new IllegalArgumentException("The text that will be encrypted is empty");
        }

        if (this._algorithm.compareTo(Algorithm.MD5) == 0)
        {
            byte[] bytes = this.md5(text);
            cipher = this.getBase64Encode(bytes);
        }
        else if (this._algorithm.compareTo(Algorithm.DES) == 0)
        {
            byte[] cipherBytes = this.EncryptDES(text.getBytes(this._encode), this._key);
            cipher = this.getBase64Encode(cipherBytes);
        }
        else
        {
            throw new InvalidAlgorithmParameterException("The algorithm is invalid.");
        }

        return cipher;
    }

    public String Decrypt(String text) throws InvalidAlgorithmParameterException, UnsupportedEncodingException
    {

        String cipher = null;

        if (text == null || "".equals(text))
        {
            throw new IllegalArgumentException("The text that will be encrypted is empty");
        }

        if (this._algorithm.compareTo(Algorithm.MD5) == 0)
        {
            throw new RuntimeException("The MD5 algorithm is a no converse arithmetic, can't decrypt it");
        }
        else if (this._algorithm.compareTo(Algorithm.DES) == 0)
        {
            byte[] cipherBytes = this.DecryptDES(this.getBase64Decode(text), this._key);
            cipher = new String(cipherBytes, this._encode);
        }
        else
        {
            throw new InvalidAlgorithmParameterException("The algorithm is invalid.");
        }

        return cipher;
    }

    public boolean verfyMD5(String text, String cipher) throws InvalidAlgorithmParameterException, UnsupportedEncodingException
    {

        if (this.Encrypt(text).equals(cipher))
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    // ----------------------- MD5 -----------------------
    /**
     * ����MD5����
     * @param  String ԭʼ��SPKEY
     * @return  byte[] ָ�����ܷ�ʽΪmd5���byte[]
     */
    private byte[] md5(String strSrc)
    {
        byte[] returnByte = null;
        try
        {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            returnByte = md5.digest(strSrc.getBytes(_encode));
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return returnByte;
    }

    // ----------------------- DES -----------------------
    /**
     * �õ�3-DES����Կ��
     * ��ݸ����Ҫ������Կ��Ϊ24���ֽڣ�md5���ܳ�4����16���ֽڣ���˺��油8���ֽڵ�0
     * @param  String ԭʼ��SPKEY
     * @return  byte[] ָ�����ܷ�ʽΪmd5���byte[]
     */
    private byte[] getEnKey(String spKey)
    {
        byte[] desKey = null;
        try
        {
            byte[] desKey1 = md5(spKey);
            desKey = new byte[24];
            int i = 0;
            while (i < desKey1.length && i < 24)
            {
                desKey[i] = desKey1[i];
                i++;
            }
            if (i < 24)
            {
                desKey[i] = 0;
                i++;
            }
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

        return desKey;
    }

    /**
     * 3-DES����
     * @param byte[] src Ҫ����3-DES���ܵ�byte[]
     * @param   byte[] enKey 3-DES������Կ
     * @return  byte[] 3-DES���ܺ��byte[]
     */

    private byte[] EncryptDES(byte[] src, byte[] enKey)
    {
        byte[] encryptedData = null;
        try
        {
            DESedeKeySpec dks = new DESedeKeySpec(enKey);
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
            SecretKey key = keyFactory.generateSecret(dks);
            Cipher cipher = Cipher.getInstance("DESede");
            cipher.init(Cipher.ENCRYPT_MODE, key);
            encryptedData = cipher.doFinal(src);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        return encryptedData;
    }

    /**
     * 
     *����3-DES���ܣ���Կ�׵�ͬ�ڼ��ܵ���Կ�ף��� 
     * @param byte[]  src Ҫ����3-DES����byte[] 
     * @param   String spkey�����SPKEY
     * @return  String 3-DES���ܺ��String
     */
    private byte[] DecryptDES(byte[] debase64, byte[] spKey)
    {
        byte[] decryptedData = null;
        try
        {
            Cipher cipher = Cipher.getInstance("DESede");
            DESedeKeySpec dks = new DESedeKeySpec(spKey);
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DESede");
            SecretKey sKey = keyFactory.generateSecret(dks);
            cipher.init(Cipher.DECRYPT_MODE, sKey);
            decryptedData = cipher.doFinal(debase64);
            //strDe = new String(ciphertext, "UTF-16LE");
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
        return decryptedData;
    }

    // ----------------------- Utility -----------------------
    /**
     * ���ַ����Base64����
     * @param byte[] src Ҫ���б�����ַ�
     * 
     * @return  String ���б������ַ�
     */

    private String getBase64Encode(byte[] src)
    {
        String requestValue = "";
        try
        {
            BASE64Encoder base64en = new BASE64Encoder();
            requestValue = base64en.encode(src);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

        return requestValue;
    }

    /**
     * ���ַ����Base64����
     * @param byte[] src Ҫ���н�����ַ�
     * 
     * @return  String ���н������ַ�
     */

    private byte[] getBase64Decode(String src)
    {
        byte[] requestValue = null;
        try
        {
            BASE64Decoder base64de = new BASE64Decoder();
            requestValue = base64de.decodeBuffer(src);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

        return requestValue;
    }

    /**
     * ȥ���ַ�Ļ��з��
     * base64����3-DES�����ʱ���õ����ַ��л��з�ţ������Ҫ����ȥ��
     */

    private String filter(String str)
    {
        String output = null;
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < str.length(); i++)
        {
            int asc = str.charAt(i);
            if (asc != 10 && asc != 13)
                sb.append(str.subSequence(i, i + 1));
        }
        output = new String(sb);
        return output;
    }
}
